package io.modifier.basic.compose.hooks.time

import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import io.modifier.basic.compose.hooks.Ref
import io.modifier.basic.compose.hooks.comm.IsActive
import io.modifier.basic.compose.hooks.comm.Options
import io.modifier.basic.compose.hooks.comm.PauseFn
import io.modifier.basic.compose.hooks.comm.ResumeFn
import io.modifier.basic.compose.hooks.useEffect
import io.modifier.basic.compose.hooks.useLatestRef
import io.modifier.basic.compose.hooks.data.useState
import kotlin.properties.Delegates
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch

/**
 * Interval options
 *
 * @constructor Create empty Interval options
 * @property initialDelay 初始调用延时
 * @property period 调用间隔
 */
@Stable
data class IntervalOptions internal constructor(
    var initialDelay: Duration = 0.seconds,
    var period: Duration = 5.seconds,
) {
    companion object : Options<IntervalOptions>(::IntervalOptions)
}

@Stable
private class Interval(private val options: IntervalOptions) {
    var ready = true
    var scope: CoroutineScope by Delegates.notNull()
    var isActiveState: MutableState<Boolean>? = null
    lateinit var intervalFn: Ref<() -> Unit>
    private lateinit var intervalJob: Job

    fun isRunning() = this::intervalJob.isInitialized && intervalJob.isActive

    fun resume() {
        if (ready) {
            scope.launch {
                if (isRunning()) return@launch
                launch {
                    delay(options.initialDelay)
                    while (isActive) {
                        intervalFn.current()
                        delay(options.period)
                    }
                }.also {
                    intervalJob = it
                    isActiveState?.value = true
                }
            }
        }
    }

    fun pause() {
        if (this::intervalJob.isInitialized && intervalJob.isActive) {
            intervalJob.cancel()
            isActiveState?.value = false
        }
    }
}

/** 间隔固定时间执行的interval函数 **/
@Composable
fun useInterval(optionsOf: IntervalOptions.() -> Unit = {}, block: () -> Unit): IntervalHolder = useInterval(
    options = remember { IntervalOptions.optionOf(optionsOf) },
    block = block
)

@Composable
fun useInterval(optionsOf: IntervalOptions.() -> Unit = {}, ready: Boolean, block: () -> Unit) = useInterval(
    remember { IntervalOptions.optionOf(optionsOf) },
    ready = ready,
    block = block
)

@Stable
data class IntervalHolder(
    val resume: ResumeFn,
    val pause: PauseFn,
    val isActive: IsActive,
)

@Composable
private fun useInterval(options: IntervalOptions = remember { IntervalOptions() }, block: () -> Unit): IntervalHolder {
    val latestFn = useLatestRef(value = block)
    val isActiveState = useState(default = false)
    val scope = rememberCoroutineScope()
    val interval = remember {
        Interval(options).apply {
            this.isActiveState = isActiveState
            this.intervalFn = latestFn
            this.scope = scope
        }
    }
    return remember {
        IntervalHolder(
            resume = interval::resume,
            pause = interval::pause,
            isActive = isActiveState
        )
    }
}

@Composable
private fun useInterval(options: IntervalOptions = remember { IntervalOptions() }, ready: Boolean, block: () -> Unit) {
    val latestFn = useLatestRef(value = block)
    val scope = rememberCoroutineScope()
    val interval = remember {
        Interval(options).apply {
            this.intervalFn = latestFn
            this.scope = scope
        }
    }.apply {
        this.ready = ready
    }
    useEffect(ready) {
        if (ready && !interval.isRunning()) {
            interval.resume()
        }
        if (!ready && interval.isRunning()) {
            interval.pause()
        }
    }
}
